package com.simafei.flow.core.rule;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ClassUtil;
import com.simafei.flow.core.ExecutionResult;
import com.simafei.flow.core.FlowConstants;
import com.simafei.flow.core.common.ListOperator;
import com.simafei.flow.core.common.Predicate;
import com.simafei.flow.core.func.Funcs;
import com.simafei.flow.core.rule.action.AssignAction;
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.core.RuleBuilder;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * @author fengpengju
 */
public class RuleExecutor {

    private final RulesEngine rulesEngine;

    private final List<ListMatcher> listMatchers;

    @Autowired
    public RuleExecutor() {
        this.rulesEngine = new DefaultRulesEngine();

        this.listMatchers = new ArrayList<>();
        try {
            for (Class<?> aClass : ClassUtil.scanPackageBySuper(getClass().getPackageName(), ListMatcher.class)) {
                ListMatcher matcher = (ListMatcher) aClass.getDeclaredConstructor().newInstance();
                listMatchers.add(matcher);
            }
        } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException ignored) {
        }
    }

    public ExecutionResult execute(FlowRules rules, Map<String, Object> params) {
        Rules engineRules = new Rules();
        for (FlowRule flowRule : rules.getFlowRules()) {
            engineRules.register(buildRule(flowRule, params));
        }

        Facts facts = buildFacts(params);
        rulesEngine.fire(engineRules, facts);

        Map<String, Object> result = facts.get(FlowConstants.RULE_RESULTS);
        return ExecutionResult.builder()
                .inputParams(params)
                .results(result.isEmpty()? List.of() : List.of(result))
                .build();
    }

    public List<ExecutionResult> execute(FlowRules rules, List<Map<String, Object>> params) {
        List<ExecutionResult> engineResults = new ArrayList<>();
        if (params.isEmpty()) {
            ExecutionResult result = execute(rules, Map.of(FlowConstants.HAS_RESULT, FlowConstants.NO));
            engineResults.add(result);
        } else {
            if (rules.isEnableListOperator() || Objects.nonNull(rules.getListOperator())) {
                Rules engineRules = new Rules();
                for (FlowRule flowRule : rules.getFlowRules()) {
                    Rule build = buildRule(rules, params, flowRule);
                    engineRules.register(build);
                }
                Facts facts = buildFacts(MapUtil.newHashMap());
                rulesEngine.fire(engineRules, facts);
                Map<String, Object> result = facts.get(FlowConstants.RULE_RESULTS);
                Map<String, Object> inputParams = MapUtil.newHashMap();
                inputParams.put(FlowConstants.PARAMS, params);
                engineResults.add(ExecutionResult.builder()
                        .inputParams(inputParams)
                        .results(result.isEmpty()? List.of() : List.of(result))
                        .build());
            } else {
                for (Map<String, Object> param : params) {
                    ExecutionResult result = execute(rules, param);
                    engineResults.add(result);
                }
            }
        }
        return engineResults;
    }

    private Rule buildRule(FlowRules rules, List<Map<String, Object>> params, FlowRule flowRule) {
        Optional<ListMatcher> opt = getMatcher(rules.getListOperator());
        RuleBuilder builder = new RuleBuilder()
                .name(flowRule.getRuleName())
                .description(flowRule.getDescription())
                .priority(Optional.ofNullable(flowRule.getPriority()).orElse(0))
                .when(facts -> opt.isPresent() && opt.get().match(flowRule.getCriteria(), params));

        Action assign = buildAssignment(flowRule);
        builder.then(assign);

        return builder.build();
    }

    private Rule buildRule(FlowRule flowRule, Map<String, Object> param) {
        RuleBuilder builder = new RuleBuilder()
                .name(flowRule.getRuleName())
                .description(flowRule.getDescription())
                .priority(Optional.ofNullable(flowRule.getPriority()).orElse(0))
                .when(facts -> Predicate.eval(flowRule.getCriteria(), param));

        Action assign = buildAssignment(flowRule);
        builder.then(assign);

        return builder.build();
    }

    private Facts buildFacts(Map<String, Object> params) {
        Facts facts = new Facts();
        // 构建Facts的时候，强制k，v都不为空，但是在实际情况下v可能为空，因为可以使用isEmpty操作符来判断，所以规则判断不用Facts来执行规则判断
        params.forEach((k, v) -> {
            if (Objects.nonNull(v)) {
                facts.put(k, v);
            }
        });
        facts.put(FlowConstants.FUNC, Funcs.INSTANCE);

        Map<String, Object> results = MapUtil.newHashMap();
        facts.put(FlowConstants.PARAMS, params);
        facts.put(FlowConstants.RULE_RESULTS, results);
        return facts;
    }

    private Action buildAssignment(FlowRule flowRule) {
        return facts -> new AssignAction(flowRule).eval(facts);
    }

    private Optional<ListMatcher> getMatcher(ListOperator operator) {
        if (CollectionUtil.isNotEmpty(listMatchers)) {
            return listMatchers.stream()
                    .filter(matcher -> matcher.support(operator))
                    .findFirst();
        }
        return Optional.empty();
    }
}
